#include "../../config.h"
#include "../log.h"

#include <glibmm/i18n.h>
#include <iconv.h>
#include <errno.h>

#include "parser.h"
#include "document.h"

namespace gpyp {
namespace conf {

//========================
// Source
const char Source::BOM_UTF8[] = {'\xef', '\xbb', '\xbf'};

void Source::normalize() {
  char *start = data_, *end = NULL;
  while(!g_utf8_validate(start, size_ + data_ - start, (const char **)&end)) {
    if (end) {
      *end = ' ';
      start = end + 1;
      end = NULL;
    } else {
      break;
    }
  }
}

void Source::clear() {
  if (data_) {
    delete [] data_;
    data_ = NULL;
  }
  capacity_ = size_ = 0;
}
bool Source::set(const char    *data,
                 size_t         size,
                 const char    *charset,
                 Glib::ustring &error_message) {
  clear();
  if (!data || !size) {
    error_message = _("Data empty");
    return false;
  }

  if (charset && strcmp(charset, "UTF-8") != 0) {
    iconv_t cd = iconv_open("UTF-8//TRANSLIT//IGNORE", charset);
    if (cd == (iconv_t)-1) {
      error_message = _("iconv open error");
      return false;
    }

    char  *_data     = NULL;
    size_t _capacity = 0;

    const char *i   = data;
    char       *o   = _data;
    size_t      is  = size, os = _capacity;
    size_t      res = 0;

    do {
      size_t copy_size = _capacity - os;
      if (!_capacity || _capacity < size) {
        /* UTF-8に変換する場合多少サイズが膨らむので2倍確保しておく。 */
        _capacity = size << 1;
      } else {
        _capacity <<= 1;
      }
      {
        char *new_data = new char[_capacity + 1];
        if (_data) {
          if (copy_size) memcpy(new_data, _data, copy_size);
          delete [] _data;
        }
        _data = new_data;
      }
      o  = _data + copy_size;
      os = _capacity - copy_size;
      while(is) {
        res = iconv(cd, ICONV_TYPE_ARG2 &i, &is, &o, &os);
        if (res == (size_t)-1) {
          if (errno == EILSEQ || // invalid multibyte sequence
              errno == EINVAL) { // incomplete multibyte sequence
            ++i;
            --is;
          } else {
            break;
          }
        }
      }
    } while(res == (size_t)-1 && errno == E2BIG);
    iconv_close(cd);
    if (res == (size_t)-1) {
      delete [] _data;
      error_message = _("iconv conversion error");
      return false;
    } else {
      data_     = _data;
      capacity_ = _capacity;
      size_     = _capacity - os;
    }
  } else {
    if (size >= sizeof(BOM_UTF8) &&
        memcmp(data, BOM_UTF8, sizeof(BOM_UTF8)) == 0) {
      data += sizeof(BOM_UTF8);
      size -= sizeof(BOM_UTF8);
    }

    data_ = new char[size + 1];
    memcpy(data_, data, size);
    capacity_ = size_ = size;
    for(size_t i = 0; i < size_; ++i) {
      if (!data_[i]) data_[i] = ' ';
    }
  }
  data_[size_] = '\0';
  normalize();
  return true;
}
bool Source::set(const char *data, size_t size) {
  Glib::ustring err;
  bool res = set(data, size, NULL, err);
  if (!res) log::err << err << log::endl;
  return res;
}

Source::operator bool() const {
  return data_ && size_ != 0;
}
bool Source::operator!() const {
  return !data_ || size_ == 0;
}

Source::Source() :
  data_    (NULL),
  capacity_(0),
  size_    (0)
{
}
Source::~Source() {
  clear();
}

//========================
// ParserBase
void ParserBase::_on_start_element(GMarkupParseContext */*context*/,
                                   const gchar         *element_name,
                                   const gchar        **attribute_names,
                                   const gchar        **attribute_values,
                                   gpointer             user_data,
                                   GError             **error) {
  ParserBase *parser = (ParserBase *)user_data;
#ifndef GMARKUP_PARSER_HAS_STACK
  parser->element_stack_ = g_slist_prepend(parser->element_stack_,
                                           g_strdup(element_name));
#endif
  parser->on_start_element(element_name,
                           attribute_names, attribute_values,
                           error);
}
void ParserBase::_on_end_element(GMarkupParseContext */*context*/,
                                 const gchar         *element_name,
                                 gpointer             user_data,
                                 GError             **error) {
  ParserBase *parser = (ParserBase *)user_data;
  parser->on_end_element(element_name, error);
#ifndef GMARKUP_PARSER_HAS_STACK
  gpointer data = g_slist_nth_data(parser->element_stack_, 0);
  g_free(data);
  parser->element_stack_ = g_slist_remove(parser->element_stack_, data);
#endif
}
void ParserBase::_on_error(GMarkupParseContext */*context*/,
                           GError              *error,
                           gpointer             user_data) {
  ParserBase *parser = (ParserBase *)user_data;
  parser->on_error(error);
}

#ifndef GMARKUP_PARSER_HAS_STACK
void ParserBase::free_element_stack() {
  if (element_stack_) {
    g_slist_foreach(element_stack_, (GFunc)g_free, NULL);
    g_slist_free(element_stack_);
    element_stack_ = NULL;
  }
}
#endif

/* もうちょっと綺麗に書きたい。 */
bool ParserBase::is_path(const GSList *&stack, const char *path) const {
  if (!stack || !stack->data || !path || path[0] != '/') return false;

  const char *stack_name = NULL;
  const char *next_slash = strchr(path + 1, '/');

  if (next_slash) {
    if (!is_path(stack, next_slash) || !stack) return false;
    stack_name = (const char *)stack->data;
    if (strncmp(stack_name, path + 1, next_slash - path - 1)) return false;
  } else {
    stack_name = (const char *)stack->data;
    if (strcmp(stack_name, path + 1)) return false;
  }
  stack = stack->next;
  return true;
}

bool ParserBase::is_path(const char *path) const {
  if (context_ && path) {
#ifdef GMARKUP_PARSER_HAS_STACK
    const GSList *stack = g_markup_parse_context_get_element_stack(context_);
#else
    const GSList *stack = element_stack_;
#endif
    return is_path(stack, path) && stack == NULL;
  }
  return false;
}

bool ParserBase::parse(const Source &source) {
  GError *error = NULL;
  context_ =
    g_markup_parse_context_new(&parser_,
                               G_MARKUP_DO_NOT_USE_THIS_UNSUPPORTED_FLAG,
                               this,
                               NULL);
#ifndef GMARKUP_PARSER_HAS_STACK
  free_element_stack();
#endif

  bool res = false;
  on_start_parsing();
  if (g_markup_parse_context_parse(context_,
                                   source.data(),
                                   source.size(),
                                   &error)) {
    res = true;
    on_complete_parsing();
    /*
  } else {
    log::err << "Conf: " << _("failed to parse file");
    if (error && error->message) {
      log::err << " - " << error->message;
    }
    log::err << log::endl;
    */
  }
  if (error) g_error_free(error);
  g_markup_parse_context_free(context_);
  context_ = NULL;
#ifndef GMARKUP_PARSER_HAS_STACK
  free_element_stack();
#endif
  return res;
}
bool ParserBase::parse(const std::string &file_path) {
  gchar  *contents = NULL;
  gsize   length   = 0;
  GError *error    = NULL;
  bool    res      = false;
  if (g_file_get_contents(file_path.c_str(), &contents, &length, &error)) {
    Source source;
    if (source.set(contents, length)) res = parse(source);
  } else {
    log::err << "Conf: " << error->message << log::endl;
  }
  if (contents) g_free(contents);
  if (error)    g_error_free(error);
  return res;
}

ParserBase::ParserBase() :
  context_(NULL)
#ifndef GMARKUP_PARSER_HAS_STACK
  ,
  element_stack_(NULL)
#endif
{
  memset(&parser_, 0, sizeof(parser_));
  parser_.start_element = ParserBase::_on_start_element;
  parser_.end_element   = ParserBase::_on_end_element;
  parser_.error         = ParserBase::_on_error;
}
ParserBase::~ParserBase() {
#ifndef GMARKUP_PARSER_HAS_STACK
  free_element_stack();
#endif
}

//========================
// ConfParser
void ConfParser::on_start_parsing() {
  stack_.reset(new std::stack<Element *>());
  current_ = NULL;
}

void ConfParser::on_start_element(const char  *element_name,
                                  const char **attribute_names,
                                  const char **attribute_values,
                                  GError     **error) {
  if (!current_) {
    if (document_.get_name() == element_name) {
      push(&document_, element_name, attribute_names, attribute_values);
      return;
    } else {
      g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                  "invalid root element - %s", element_name);
    }
  } else {
    current_->on_start_element(*this, element_name,
                               attribute_names, attribute_values, error);
  }
}
void ConfParser::on_end_element(const char *element_name,
                                GError    **error) {
  current_->on_end_element(*this, element_name, error);
}
void ConfParser::on_error(GError *error) {
  log::err << "Conf: " << _("failed to parse file");
  if (error && error->message) {
    log::err << " - " << error->message;
  }
  log::err << log::endl;
}

void ConfParser::push(Element     *element,
                      const char  *element_name,
                      const char **attribute_names,
                      const char **attribute_values) {
  if (current_) stack_->push(current_);
  current_ = element;
  current_->on_load(*this, element_name, attribute_names, attribute_values);
}
void ConfParser::pop() {
  if (stack_->empty()) {
    current_ = NULL;
  } else {
    current_ = stack_->top();
    stack_->pop();
  }
}

ConfParser::ConfParser(Document &document) :
  document_(document)
{
}

}/*conf*/
}/*gpyp*/
